package model 
{
	import flash.errors.InvalidSWFError;
	import model.observer.IModel;
	import model.Constants;
	import model.observer.IView;
	import model.rotationSystem.IRotationSystem;
	import model.rotationSystem.SuperRotationSystem;
	import model.Shapes.ShapeI;
	import model.Shapes.ShapeO;
	import model.Shapes.TetrisShape;
	import model.Point;
	/**
	 * Represents the Tetris Board
	 * the board coordinate (0,0) is the bottom-left corner of the grid.
	 * @author Yang Wang
	 */
	public class Board implements IModel
	{
		private var m_views:Array;
		private var m_grid:Array;
		
		private var m_shape:TetrisShape;
		private var m_shapePosition:Point;
		private var m_ghost:TetrisShape;
		
		private var m_topOfEachColumn:Array;
		
		private var m_changes:Object;
		private var m_rotationSystem:IRotationSystem;
		
		public function Board() 
		{
			this.m_shapePosition = new Point(0, 0);
			this.m_views = new Array();	
			var ab:ArrayBuilder = new ArrayBuilder();
			this.m_grid = ab.build2DArray(Constants.BOARD_WIDTH, Constants.BOARD_HEIGHT);
			this.m_topOfEachColumn = ab.buildArray(Constants.BOARD_WIDTH);
			this.m_rotationSystem = new SuperRotationSystem(this);
		}

	
		private function getShapeStartPoint(shape:TetrisShape) : Point {
			var startX:int = 3;
			if (shape is ShapeI || shape is ShapeO)
				startX = 4;
			return new Point(startX, this.height);
		}
		
		public function canAddNewShape(shape:TetrisShape) : Boolean 
		{
			return isValidShapePosition(this.getShapeStartPoint(shape), shape.getSquares(shape.Orientation));
		}
		
		public function isValidShapePosition(pos:Point, squares:Array) :Boolean
		{			
 			for (var i:int = 0; i < squares.length; i++) {
				var col:Array = squares[i] as Array;
				for (var j:int = 0; j < col.length; j++) {
					var point:Point = new Point(pos.X + i, pos.Y - col.length + 1 + j);
					if (squares[i][j] != true) {
						continue;
					} else if (!pointWithinBoundary(point)) {
						return false;
					} else if (this.isFilled(point)) {
						return false;
					}
				}
			}
			return true;
		}
		
		public function pointWithinBoundary(point:Point) : Boolean {
			if (point.X >= 0 && point.Y >= 0 && point.X < this.width) {
				return true;
			}
			return false;
		}
		
		public function getCell(row:uint, col:uint) : int {
			return m_grid[row][col];
		}
		
		public function isEmpty(point:Point) : Boolean
		{
			return m_grid[point.X][point.Y] == null;
		}
		
		public function isFilled(point:Point) : Boolean
		{
			return ! isEmpty(point);
		}
		
		public function setSquare(x:int, y:int, color:uint) : void {
			this.m_grid[x][y] = color;
		}
		
		public function unsetSquare(x:int , y:int) : void  {
			this.m_grid[x][y] = null;
		}
		
		public function get width() : uint
		{
			return m_grid.length;
		}
		
		public function get height() : uint
		{
			return (m_grid[0] as Array).length;
		}
		
		public function get currentShape():TetrisShape 
		{
			return m_shape;
		}
		
		public function set currentShape(value:TetrisShape):void 
		{
			m_shape = value;
			
			if (value == null)
				return;
				
			this.shapePosition = this.getShapeStartPoint(value);
			
			trace("current shape is " + value);
			this.m_changes = { newShape: true };
			notifyViews();
		}
		
		public function get shapePosition():Point 
		{
			return m_shapePosition;
		}
		
		public function set shapePosition(value:Point):void 
		{
			m_shapePosition = value;
			notifyViews();
		}
		
		public function putShape(point:Point) : void {
			m_shapePosition = point;
			notifyViews();
		}
		
		/* INTERFACE model.observer.IModel */
		
		public function addView(view:IView):void 
		{
			this.m_views.push(view);
		}
		
		public function removeView(view:IView):void 
		{
			m_views.splice(m_views.indexOf(view), 1);
		}
		
		public function notifyViews() : void {
			for (var i:int = 0; i < m_views.length; i++) {
				var view :IView = m_views[i] as IView;
				view.updateView(m_changes);
			}
			m_changes = new Object();
		}
		
		public function moveShapeLeft():void 
		{	
			var point:Point = new model.Point(shapePosition.X - 1, shapePosition.Y);
			
			if (isValidShapePosition(point, this.currentShape.getSquares(this.currentShape.Orientation)))
				this.shapePosition = point;
		}
		
				
		public function moveShapeRight():void 
		{	
			var point:Point = new model.Point(shapePosition.X + 1, shapePosition.Y);
			
			if (isValidShapePosition(point, this.currentShape.getSquares(this.currentShape.Orientation)))
				this.shapePosition = point;
		}
		
		public function softDrop() : Boolean 
		{
			var point:Point = new model.Point(shapePosition.X, shapePosition.Y - 1);
			
			if (isValidShapePosition(point, this.currentShape.getSquares(this.currentShape.Orientation))) {
				this.shapePosition = point;
				return true;
			}
			return false;
		}
		 
		public function hardDrop() : void {
			this.shapePosition = getHardDropFinalLocation();
			dissolveShape();
			clearLines();
			notifyViews();
		}
		
		public function getHardDropFinalLocation() : Point {
			var point:Point = new model.Point(shapePosition.X, shapePosition.Y);
			var y:int = 0;
			while (isValidShapePosition(point, this.currentShape.getSquares(this.currentShape.Orientation))) {
				y = point.Y;
				point = new Point(point.X, point.Y - 1);
			}
			return new Point(shapePosition.X, y);
		}
		
		public function toBoardCoordinate(shapeSquarePos:Point) :Point {
			return new Point(this.shapePosition.X + shapeSquarePos.X, 
				this.shapePosition.Y - this.currentShape.height + shapeSquarePos.Y + 1); 
		}
		
		public function dissolveShape() : void {
			for (var i:int = 0; i < this.currentShape.width; i++) {
				for (var j:int = 0; j < this.currentShape.height; j++) {
					if(currentShape.hasSquareAt(i,j)) {
						var point:Point = this.toBoardCoordinate(new Point(i, j));
						this.setSquare(point.X, point.Y, currentShape.color);
					}
				}
			}
			this.currentShape = null;
			this.notifyViews();
		}
		
		public function getLinesToClear() : Array {
			var lines:Array = new Array();
			
			for (var row:int = 0; row < this.height; row++) {
				var column: int = 0;
				for (column = 0; column < this.width; column++) {
					if (isEmpty(new Point(column, row)))
						break;
				}
				
				if (column == this.width - 1) {
					lines.push(row);
				}
			}
			return lines;
		}
		
		public function clearLines() : Array {
			var lines:Array = new Array();
			var rowsCleared:int = 0;
			
			for(var row:int = 0; row < this.height; row++) {
				var column: int = 0;
				for (column = 0; column < this.width; column++) {
					if (isEmpty(new model.Point(column, row)))
						break;
				}
				
				if (column == this.width) {
					for (column = 0; column < this.width; column++) {
						trace(new Point(column, row));
						this.unsetSquare(column, row);
					}
					trace(this.toString());
					rowsCleared++;
					lines.push(row);
					trace("cleared line " + row);
				} else if (rowsCleared > 0) {
					trace("shiftin down " + rowsCleared + " rows");
					for (column = 0; column < this.width; column++) {
						if (this.isFilled(new Point(column, row))) {
							this.m_grid[column][row - rowsCleared] = this.m_grid[column][row];
							this.unsetSquare(column, row);
						}
					}
				} else {
					trace("nothing to clear");
				}
			}
			
			return lines;
		}
		
		public function toString() : String {
			var result:String = new String();
			for (var j:int = this.height - 1; j >= 0; j--) {
				result += "" + j;
				for (var i:int = 0; i < this.width; i++) {
					result += this.isFilled(new Point(i, j)) ? 'x' : '_';
				}
				result += "\n";
			}
			return result;
		}
		
		public function rotateLeft() : void {
			this.m_rotationSystem.rotateLeft();
		}
		
		public function rotateRight() : void {
			this.m_rotationSystem.rotateRight();
		}
	}

}